package airacrobats.game;

import airacrobats.game.object.GameObject;
import airacrobats.game.object.Terrain;
import airacrobats.sound.Sound;
import airacrobats.sound.Sound3D;
import airacrobats.util.Util;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.glu.GLU;
import javax.swing.JPanel;
import javax.vecmath.Point3f;
import javax.vecmath.Point4f;
import javax.vecmath.Tuple4f;

/**
 * This is the camera class. Even tough the camera has many similarities with a game object, it is NOT
 * a game object!
 */
public class Camera
{
    /**
     * The info panel size.
     */
    private static final Dimension INFO_PANEL_SIZE = new Dimension(120, 20);
    /**
     * The speed where the sound of the camera has the max gain.
     */
    private static final float MAX_SOUND_SPEED_GAIN = 35.0f;
    /**
     * The camera sight (far position).
     */
    public final static float SIGHT = 2000.0f;

    /**
     * The game object the came will follow.
     */
    private GameObject gameObject;
    /**
     * The camera position.
     */
    private Point3f position;
    /**
     * The GLU.
     */
    private GLU glu;
    /**
     * Look at camera flag.
     */
    private boolean lookAtCamera;
    /**
     * Spectator flag.
     */
    private boolean spectator;
    /**
     * The vision structure, containing the x-y-z relative position of the camera and the angle.
     */
    private Tuple4f visionStructure;
    /**
     * Camera sound.
     */
    private Sound3D sound;
    
    /**
     * @param gameObject The game object.
     * @param sound Camera sound.
     */
    public Camera(GameObject gameObject, Sound3D sound)
    {
        this.gameObject = gameObject;
        this.sound = sound;
        this.position = new Point3f(gameObject.getPosition());
        glu = new GLU();
        visionStructure = new Point4f(-0.2f, 0.3f, 0.0f, 30);
    }

    /**
     * Transforms the scene perspective.
     * @param drawable The drawable context.
     */
    public void transformProjection(GLAutoDrawable drawable)
    {
        glu.gluPerspective(60, 1.0f, 0.1f, SIGHT);
    }

    /**
     * Transforms the model view of the camera.
     * @param drawable The drawable context.
     */
    public void transformModelview(GLAutoDrawable drawable)
    {
        if (lookAtCamera)
        {
            glu.gluLookAt(position.x + visionStructure.x, position.y + visionStructure.y, position.z + visionStructure.z,
                    gameObject.getPosition().x + 2.0f, gameObject.getPosition().y, gameObject.getPosition().z,
                    0.0f, 1.0f, 0.0f);
        }
        else
        {
            GL2 gl = drawable.getGL().getGL2();

            // Because it rotates on the y axis, x and z are switched. 
            // Also, the y position has it signal inverted:
            // Remember that the coordinate system is beeing changed.
            gl.glTranslatef(visionStructure.z, -visionStructure.y, visionStructure.x);
            gl.glRotatef(90, 0.0f, 1.0f, 0.0f);
            gl.glRotatef(visionStructure.w, 0.0f, 0.0f, 1.0f);

            gameObject.transformModelview(gl);
            gl.glTranslatef(-position.x, -position.y, -position.z);
        }
    }

    /**
     * @return The game object.
     */
    public GameObject getGameObject()
    {
        return gameObject;
    }

    /**
     * Creates the info panel and adds it to the owner panel.
     * @param owner The owner panel.
     * @param c The grid bag constraints.
     */
    public void createInfoPanel(JPanel owner, GridBagConstraints c)
    {
        JPanel panel = new JPanel()
        {
            @Override
            protected void paintComponent(Graphics g)
            {
                int height = g.getFontMetrics().getHeight();
                if (spectator)
                    Util.drawOutlinedString("Spectator camera", Color.WHITE, Color.BLACK, g, 2, height);
                else if (lookAtCamera)
                    Util.drawOutlinedString("Far camera", Color.WHITE, Color.BLACK, g, 2, height);
                else
                    Util.drawOutlinedString("Custom camera", Color.WHITE, Color.BLACK, g, 2, height);
            }
        };
        panel.setPreferredSize(INFO_PANEL_SIZE);
        panel.setMinimumSize(INFO_PANEL_SIZE);
        owner.add(panel, c);
    }
            
    /**
     * Updates the camera.
     * @param deltaTime The delta time.
     * @param time The time elapsed.
     * @throws Exception Thrown if an error occurs while updating the game object of the camera.
     */
    public void update(long deltaTime, long time) throws Exception
    {
        if (deltaTime > 0)
        {
            doUpdate(time);
        }
    }

    /**
     * @return The sound.
     */
    public Sound3D getSound()
    {
        return sound;
    }

    /**
     * @return The position.
     */
    public Point3f getPosition()
    {
        return position;
    }

    /**
     * @return True if it is spectator.
     */
    public boolean isSpectator()
    {
        return spectator;
    }

    /**
     * @return The vision structure.
     */
    public Tuple4f getVisionStructure()
    {
        return visionStructure;
    }

    /**
     * @param visionStructure The new vision structure.
     */
    public void setVisionStructure(Tuple4f visionStructure)
    {
        this.visionStructure = visionStructure;
    }

    /**
     * Changes the camera type.
     * @param spectator Spectator flag.
     * @param lookAtCamera Look at flag.
     */
    public void changeCamera(boolean spectator, boolean lookAtCamera)
    {
        this.spectator = spectator;
        this.lookAtCamera = lookAtCamera || spectator;

        if (spectator)
        {
            visionStructure = new Point4f();
            position = new Point3f(0.0f, Terrain.SHADOW_MIN_HEIGHT + 20.0f, 0.0f);
        }
    }

    /**
     * Updates the camera.
     * @param time The stage time.
     * @throws Exception Thrown if an error occurs while updating the game object of the camera.
     */
    private void doUpdate(long time) throws Exception
    {
        // If it is a spectator camera, it is mantained fixed.
        // OBS: In the replay, the camera does not have a sound.
        if (!spectator)
        {
            position = new Point3f(gameObject.getPosition());

            if (sound == null)
                return;
            
            if (!sound.isPlaying())
                sound.play();

            // The minimum gain is divided, so the camera sound can be heard better.
            float gain = (Sound.MAX_GAIN - Sound.MIN_GAIN / 2.0f) / MAX_SOUND_SPEED_GAIN *
                    gameObject.getSpeed() + Sound.MIN_GAIN / 2.0f;

            sound.setGain(gain);
        }
        else
        {
            if (sound != null)
                sound.stop();
        }
    }
}
